package com.ruoyi.common.utils.opcua;

import org.eclipse.milo.opcua.sdk.core.AccessLevel;
import org.eclipse.milo.opcua.sdk.core.Reference;
import org.eclipse.milo.opcua.sdk.server.Lifecycle;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.api.DataItem;
import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle;
import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;
import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
import org.eclipse.milo.opcua.sdk.server.dtd.DataTypeDictionaryManager;
import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.BaseEventTypeNode;
import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte;
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;

/**
 * OPC UA命名空间工具类，支持自动类型转换和节点备注说明
 * 提供节点创建、更新、读取等核心功能，支持批量操作
 */
public class OpcUaServerUtil extends ManagedNamespaceWithLifecycle {
    public static final String NAMESPACE_URI = "urn:eclipse:milo:opc";
    private static final String ROOT_FOLDER_NAME = "opc";

    // 单例实例
    private static volatile OpcUaServerUtil instance;

    private static final Logger logger = LoggerFactory.getLogger(OpcUaServerUtil.class);
    private volatile Thread eventThread;
    private volatile boolean keepPostingEvents = true;
    private final SubscriptionModel subscriptionModel;
    // 缓存节点信息，提高查询效率
    private final Map<String, UaVariableNode> nodeCache = new ConcurrentHashMap<>();
    // 缓存节点备注信息
    private final Map<String, String> nodeDescriptions = new ConcurrentHashMap<>();
    // 根文件夹节点
    private UaFolderNode rootFolderNode;
    // OPC UA服务器实例
    private final OpcUaServer server;
    // 当前连接的主机和端口
    private String currentHost;
    private int currentPort;

    /**
     * 私有构造函数，防止外部实例化
     * @param server OPC UA服务器实例
     */
    private OpcUaServerUtil(OpcUaServer server) {
        super(server, NAMESPACE_URI);

        this.server = server;
        subscriptionModel = new SubscriptionModel(server, this);
        DataTypeDictionaryManager dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), NAMESPACE_URI);

        getLifecycleManager().addLifecycle(dictionaryManager);
        getLifecycleManager().addLifecycle(subscriptionModel);

        getLifecycleManager().addLifecycle(new Lifecycle() {
            @Override
            public void startup() {
                initializeRootFolder();
                startBogusEventNotifier();
            }

            @Override
            public void shutdown() {
                try {
                    keepPostingEvents = false;
                    if (eventThread != null) {
                        eventThread.interrupt();
                        eventThread.join();
                    }
                } catch (InterruptedException ignored) {
                }
                nodeCache.clear();
                nodeDescriptions.clear();
            }
        });
    }

    /**
     * 获取单例实例
     * @param host 服务器绑定的主机地址（如"0.0.0.0"表示所有网络接口）
     * @param port 服务器监听的端口（如12688）
     * @return OpcUaServerUtil单例实例
     */
    public static OpcUaServerUtil getInstance(String host, int port) {
        if (instance == null) {
            synchronized (OpcUaServerUtil.class) {
                if (instance == null) {
                    OpcUaServer server = createServer(host, port);
                    instance = new OpcUaServerUtil(server);
                    instance.currentHost = host;
                    instance.currentPort = port;
                    instance.startup();
                } else {
                    if (!instance.currentHost.equals(host) || instance.currentPort != port) {
                        shutdownInstance();
                        OpcUaServer server = createServer(host, port);
                        instance = new OpcUaServerUtil(server);
                        instance.currentHost = host;
                        instance.currentPort = port;
                        instance.startup();
                    }
                }
            }
        }
        return instance;
    }

    /**
     * 批量更新或创建节点（支持添加备注说明）
     * @param host 服务器主机地址
     * @param port 服务器端口
     * @param nodeInfos 节点信息列表，包含节点名称、值和备注
     */
    public static void updateNodesWithDescription(String host, int port, List<NodeInfo> nodeInfos) {
        OpcUaServerUtil instance = getInstance(host, port);
        instance.batchUpdateOrCreateNodesWithDescription(nodeInfos);
    }

    /**
     * 批量更新或创建节点（支持自动类型转换）
     * @param host 服务器主机地址
     * @param port 服务器端口
     * @param nodeValues 节点名称与值的映射表
     *                   键：节点名称（字符串，需唯一标识一个节点）
     *                   值：节点的初始值或更新值（支持Boolean、Number、String、Date等类型）
     */
    public static void updateNodes(String host, int port, Map<String, Object> nodeValues) {
        // 转换为NodeInfo列表，不包含备注
        List<NodeInfo> nodeInfos = nodeValues.entrySet().stream()
                .map(entry -> new NodeInfo(entry.getKey(), entry.getValue(), null))
                .collect(Collectors.toList());
        updateNodesWithDescription(host, port, nodeInfos);
    }

    /**
     * 获取节点的备注说明
     * @param host 服务器主机地址
     * @param port 服务器端口
     * @param nodeName 节点名称
     * @return 节点的备注说明，若不存在则返回null
     */
    public static String getNodeDescription(String host, int port, String nodeName) {
        OpcUaServerUtil instance = getInstance(host, port);
        return instance.nodeDescriptions.get(nodeName);
    }

    /**
     * 批量获取节点的备注说明
     * @param host 服务器主机地址
     * @param port 服务器端口
     * @param nodeNames 节点名称集合
     * @return 节点名称与备注说明的映射表
     */
    public static Map<String, String> getNodeDescriptions(String host, int port, Collection<String> nodeNames) {
        OpcUaServerUtil instance = getInstance(host, port);
        Map<String, String> result = new HashMap<>();
        for (String nodeName : nodeNames) {
            result.put(nodeName, instance.nodeDescriptions.get(nodeName));
        }
        return result;
    }

    /**
     * 批量读取节点值
     * @param host 服务器主机地址
     * @param port 服务器端口
     * @param nodeNames 要读取的节点名称集合
     * @return 节点名称与对应值的映射表
     */
    public static Map<String, Object> readNodes(String host, int port, Collection<String> nodeNames) {
        OpcUaServerUtil instance = getInstance(host, port);
        return instance.batchReadNodes(nodeNames);
    }

    /**
     * 静态关闭方法，用于停止服务器并释放资源
     */
    public static void shutdownServer() {
        shutdownInstance();
    }

    private static void shutdownInstance() {
        if (instance != null) {
            synchronized (OpcUaServerUtil.class) {
                if (instance != null) {
                    try {
                        logger.info("正在关闭OPC UA服务器...");
                        instance.shutdown();
                        if (instance.server != null) {
                            instance.server.shutdown().get(5, TimeUnit.SECONDS);
                        }
                        logger.info("OPC UA服务器已关闭");
                    } catch (Exception e) {
                        logger.error("关闭服务器时发生错误: {}", e.getMessage(), e);
                    } finally {
                        instance = null;
                    }
                }
            }
        }
    }

    /**
     * 初始化根文件夹，作为所有节点的父节点
     */
    private void initializeRootFolder() {
        NodeId folderNodeId = newNodeId(ROOT_FOLDER_NAME);
        rootFolderNode = new UaFolderNode(
                getNodeContext(),
                folderNodeId,
                newQualifiedName(ROOT_FOLDER_NAME),
                LocalizedText.english(ROOT_FOLDER_NAME)
        );
        getNodeManager().addNode(rootFolderNode);

        rootFolderNode.addReference(new Reference(
                rootFolderNode.getNodeId(),
                Identifiers.Organizes,
                Identifiers.ObjectsFolder.expanded(),
                false
        ));
        logger.info("初始化根文件夹: {}", ROOT_FOLDER_NAME);
    }

    /**
     * 批量更新或创建节点（支持备注说明）
     * @param nodeInfos 节点信息列表，包含名称、值和备注
     */
    private void batchUpdateOrCreateNodesWithDescription(List<NodeInfo> nodeInfos) {
        if (nodeInfos == null || nodeInfos.isEmpty()) {
            logger.warn("节点信息列表为空，不执行任何操作");
            return;
        }

        for (NodeInfo info : nodeInfos) {
            String nodeName = info.nodeName;
            Object value = info.value;
            String description = info.description;

            UaVariableNode node = nodeCache.get(nodeName);

            if (node != null) {
                // 更新节点值
                try {
                    Object convertedValue = convertValueToNodeType(node, value);
                    updateNodeValue(node, convertedValue);
                } catch (UaException e) {
                    logger.error("更新节点 {} 失败: {}", nodeName, e.getMessage());
                }

                // 如果提供了新的备注，更新备注
                if (description != null) {
                    updateNodeDescription(node, description);
                    nodeDescriptions.put(nodeName, description);
                }
            } else {
                // 创建新节点，包含备注
                node = createNode(nodeName, value, description);
                if (node != null) {
                    nodeCache.put(nodeName, node);
                    rootFolderNode.addOrganizes(node);
                    // 缓存备注信息
                    if (description != null) {
                        nodeDescriptions.put(nodeName, description);
                    }
                    logger.info("创建新节点: {}，值类型: {}, 备注: {}",
                            nodeName,
                            value != null ? value.getClass().getName() : "null",
                            description);
                }
            }
        }
    }

    /**
     * 批量读取节点值
     * @param nodeNames 要读取的节点名称集合
     * @return 节点名称与值的映射表，不存在的节点对应值为null
     */
    private Map<String, Object> batchReadNodes(Collection<String> nodeNames) {
        Map<String, Object> result = new HashMap<>(nodeNames.size());

        if (nodeNames == null || nodeNames.isEmpty()) {
            logger.warn("节点名称集合为空，不执行任何操作");
            return result;
        }

        for (String nodeName : nodeNames) {
            UaVariableNode node = nodeCache.get(nodeName);
            if (node != null) {
                Object value = readNodeValue(node);
                result.put(nodeName, value);
                logger.debug("读取节点 {} 的值: {}", nodeName, value);
            } else {
                logger.warn("节点 {} 不存在", nodeName);
                result.put(nodeName, null);
            }
        }

        return result;
    }

    /**
     * 创建单个节点（支持添加备注说明）
     * @param nodeName 节点名称（必须唯一，建议使用有意义的名称如"temperature"、"pressure"）
     * @param initialValue 节点初始值，决定节点的数据类型：
     *                    - Boolean → 布尔类型节点
     *                    - Byte/Short/Integer/Long → 整数类型节点
     *                    - Float/Double → 浮点类型节点
     *                    - String → 字符串类型节点
     *                    - Date → 日期时间类型节点
     *                    - 其他类型 → 自动转为String类型
     * @param description 节点备注说明（可选），将存储在节点的Description属性中
     * @return 创建成功的UaVariableNode对象，失败返回null
     */
    private UaVariableNode createNode(String nodeName, Object initialValue, String description) {
        try {
            // 根据初始值确定节点数据类型
            NodeId typeId = getNodeIdForValue(initialValue);
            Variant variant = new Variant(initialValue);

            // 构建节点基本信息
            UaVariableNode.UaVariableNodeBuilder nodeBuilder = new UaVariableNode.UaVariableNodeBuilder(getNodeContext())
                    .setNodeId(newNodeId(nodeName))  // 节点唯一标识
                    .setAccessLevel(AccessLevel.READ_WRITE)  // 读写权限
                    .setUserAccessLevel(AccessLevel.READ_WRITE)  // 用户读写权限
                    .setBrowseName(newQualifiedName(nodeName))  // 浏览名称
                    .setDisplayName(LocalizedText.english(nodeName))  // 显示名称
                    .setDataType(typeId)  // 数据类型
                    .setTypeDefinition(Identifiers.BaseDataVariableType);  // 类型定义

            // 设置备注说明（Description属性）
            if (description != null && !description.isEmpty()) {
                nodeBuilder.setDescription(LocalizedText.english(description));
            }

            UaVariableNode node = nodeBuilder.build();

            // 设置初始值
            node.setValue(new DataValue(variant));
            getNodeManager().addNode(node);
            return node;
        } catch (Exception e) {
            logger.error("创建节点 {} 失败: {}", nodeName, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 更新节点的备注说明
     * @param node 目标节点
     * @param description 新的备注说明
     */
    private void updateNodeDescription(UaVariableNode node, String description) {
        if (description == null || description.isEmpty()) {
            logger.warn("节点 {} 的备注说明为空，不执行更新", node.getBrowseName().getName());
            return;
        }

        try {
            node.setDescription(LocalizedText.english(description));
            logger.debug("更新节点 {} 的备注说明: {}", node.getBrowseName().getName(), description);
        } catch (Exception e) {
            logger.error("更新节点 {} 的备注说明失败: {}", node.getBrowseName().getName(), e.getMessage(), e);
        }
    }

    /**
     * 核心改进：将值转换为节点所需的类型
     * @param node 目标节点，包含数据类型信息
     * @param value 待转换的值
     * @return 转换后与节点类型匹配的值
     * @throws UaException 转换失败时抛出异常
     */
    private Object convertValueToNodeType(UaVariableNode node, Object value) throws UaException {
        if (value == null) {
            return null;
        }

        NodeId dataTypeId = node.getDataType();
        Class<?> valueClass = value.getClass();

        // 如果类型已匹配，直接返回
        if (isTypeMatch(dataTypeId, valueClass)) {
            return value;
        }

        // 尝试类型转换
        try {
            if (dataTypeId.equals(Identifiers.Boolean) && value instanceof Number) {
                return ((Number) value).intValue() != 0;  // 数字转布尔（0→false，非0→true）
            } else if (dataTypeId.equals(Identifiers.SByte)) {
                return ((Number) value).byteValue();  // 转8位有符号整数
            } else if (dataTypeId.equals(Identifiers.Int16)) {
                return ((Number) value).shortValue();  // 转16位有符号整数
            } else if (dataTypeId.equals(Identifiers.Int32)) {
                return ((Number) value).intValue();  // 转32位有符号整数
            } else if (dataTypeId.equals(Identifiers.Int64)) {
                return ((Number) value).longValue();  // 转64位有符号整数
            } else if (dataTypeId.equals(Identifiers.Float)) {
                return ((Number) value).floatValue();  // 转32位浮点数
            } else if (dataTypeId.equals(Identifiers.Double)) {
                return ((Number) value).doubleValue();  // 转64位浮点数
            } else if (dataTypeId.equals(Identifiers.String)) {
                return value.toString();  // 转字符串
            } else if (dataTypeId.equals(Identifiers.DateTime) && value instanceof Date) {
                return new DateTime((Date) value);  // 转OPC UA日期时间
            } else {
                throw new UaException(StatusCode.BAD, String.format("无法将 %s 转换为节点所需类型 %s", valueClass.getName(), getTypeName(dataTypeId)));
            }
        } catch (ClassCastException e) {
            throw new UaException(StatusCode.BAD, String.format("类型转换失败: %s -> %s", valueClass.getName(), getTypeName(dataTypeId)));
        }
    }

    /**
     * 检查值类型是否与节点数据类型匹配
     */
    private boolean isTypeMatch(NodeId dataTypeId, Class<?> valueClass) {
        if (dataTypeId.equals(Identifiers.Boolean) && Boolean.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.SByte) && Byte.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.Int16) && Short.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.Int32) && Integer.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.Int64) && Long.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.Float) && Float.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.Double) && Double.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.String) && String.class.equals(valueClass)) {
            return true;
        } else if (dataTypeId.equals(Identifiers.DateTime) && Date.class.equals(valueClass)) {
            return true;
        }
        return false;
    }

    /**
     * 获取数据类型名称
     */
    private String getTypeName(NodeId dataTypeId) {
        if (dataTypeId.equals(Identifiers.Boolean)) return "Boolean";
        if (dataTypeId.equals(Identifiers.SByte)) return "SByte";
        if (dataTypeId.equals(Identifiers.Int16)) return "Int16";
        if (dataTypeId.equals(Identifiers.Int32)) return "Int32";
        if (dataTypeId.equals(Identifiers.Int64)) return "Int64";
        if (dataTypeId.equals(Identifiers.Float)) return "Float";
        if (dataTypeId.equals(Identifiers.Double)) return "Double";
        if (dataTypeId.equals(Identifiers.String)) return "String";
        if (dataTypeId.equals(Identifiers.DateTime)) return "DateTime";
        return dataTypeId.toString();
    }

    /**
     * 根据值获取对应的OPC UA NodeId
     */
    private NodeId getNodeIdForValue(Object value) {
        if (value == null) {
            return Identifiers.BaseDataType;
        } else if (value instanceof Boolean) {
            return Identifiers.Boolean;
        } else if (value instanceof Byte) {
            return Identifiers.SByte;
        } else if (value instanceof Short) {
            return Identifiers.Int16;
        } else if (value instanceof Integer) {
            return Identifiers.Int32;
        } else if (value instanceof Long) {
            return Identifiers.Int64;
        } else if (value instanceof Float) {
            return Identifiers.Float;
        } else if (value instanceof Double) {
            return Identifiers.Double;
        } else if (value instanceof String) {
            return Identifiers.String;
        } else if (value instanceof Date) {
            return Identifiers.DateTime;
        } else {
            logger.warn("不支持的节点数据类型: {}，将使用String类型替代", value.getClass().getName());
            return Identifiers.String;
        }
    }

    /**
     * 更新节点值（使用转换后的值）
     */
    private void updateNodeValue(UaVariableNode node, Object convertedValue) {
        try {
            if (convertedValue == null) {
                logger.warn("节点 {} 的更新值为null，不执行更新", node.getBrowseName().getName());
                return;
            }

            DataValue newValue = new DataValue(
                    new Variant(convertedValue),
                    StatusCode.GOOD,
                    DateTime.now()
            );

            node.setValue(newValue);
            logger.debug("更新节点 {} 的值为: {} (类型: {})",
                    node.getBrowseName().getName(),
                    convertedValue,
                    convertedValue.getClass().getName());
        } catch (Exception e) {
            logger.error("更新节点 {} 失败: {}", node.getBrowseName().getName(), e.getMessage(), e);
        }
    }

    /**
     * 读取节点值
     */
    private Object readNodeValue(UaVariableNode node) {
        try {
            DataValue value = node.getValue();
            if (value != null && value.getValue() != null) {
                return value.getValue().getValue();
            } else {
                logger.warn("节点 {} 的值为空", node.getBrowseName().getName());
                return null;
            }
        } catch (Exception e) {
            logger.error("读取节点 {} 失败: {}", node.getBrowseName().getName(), e.getMessage(), e);
            return null;
        }
    }

    /**
     * 移除节点（包括备注信息）
     * @param nodeNames 要移除的节点名称集合
     */
    public void removeNodes(Set<String> nodeNames) {
        if (nodeNames == null || nodeNames.isEmpty()) {
            return;
        }

        for (String nodeName : nodeNames) {
            UaVariableNode node = nodeCache.remove(nodeName);
            // 移除备注信息
            nodeDescriptions.remove(nodeName);

            if (node != null) {
                getNodeManager().removeNode(node.getNodeId());
                rootFolderNode.removeReference(new Reference(
                        rootFolderNode.getNodeId(),
                        Identifiers.Organizes,
                        node.getNodeId().expanded(),
                        false
                ));
                logger.info("移除节点: {}", nodeName);
            }
        }
    }

    private void startBogusEventNotifier() {
        UaNode serverNode = getServer()
                .getAddressSpaceManager()
                .getManagedNode(Identifiers.Server)
                .orElse(null);

        if (serverNode instanceof ServerTypeNode) {
            ((ServerTypeNode) serverNode).setEventNotifier(ubyte(1));

            eventThread = new Thread(() -> {
                while (keepPostingEvents) {
                    try {
                        BaseEventTypeNode eventNode = getServer().getEventFactory().createEvent(
                                newNodeId(UUID.randomUUID()),
                                Identifiers.BaseEventType
                        );
                        eventNode.setBrowseName(new QualifiedName(getNamespaceIndex(), "Event"));
                        eventNode.setDisplayName(LocalizedText.english("Event"));
                        eventNode.setEventId(ByteString.of(new byte[]{0, 1, 2, 3}));
                        eventNode.setEventType(Identifiers.BaseEventType);
                        eventNode.setSourceNode(serverNode.getNodeId());
                        eventNode.setSourceName(serverNode.getDisplayName().getText());
                        eventNode.setTime(DateTime.now());
                        eventNode.setReceiveTime(DateTime.NULL_VALUE);
                        eventNode.setMessage(LocalizedText.english("Event message from OPC UA Server"));
                        eventNode.setSeverity(ushort(2));

                        getServer().getEventBus().post(eventNode);
                        eventNode.delete();
                    } catch (Throwable e) {
                        logger.error("创建事件节点错误: {}", e.getMessage(), e);
                    }

                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException ignored) {
                    }
                }
            }, "event-poster");
            eventThread.start();
        }
    }

    /**
     * 创建OPC UA服务器
     * @param host 绑定的主机地址
     * @param port 监听的端口
     * @return 创建成功的OpcUaServer实例
     */
    private static OpcUaServer createServer(String host, int port) {
        try {
            EndpointConfiguration endpointConfiguration = EndpointConfiguration.newBuilder()
                    .setBindAddress(host)
                    .setBindPort(port)
                    .build();

            Set<EndpointConfiguration> endpointConfigurations = new HashSet<>();
            endpointConfigurations.add(endpointConfiguration);

            OpcUaServerConfig serverConfig = OpcUaServerConfig.builder()
                    .setApplicationName(LocalizedText.english("OPC UA Server"))
                    .setApplicationUri("urn:eclipse:milo:examples:server")
                    .setProductUri("urn:eclipse:milo:examples:server")
                    .setEndpoints(endpointConfigurations)
                    .build();

            OpcUaServer server = new OpcUaServer(serverConfig);
            server.startup().get(5, TimeUnit.SECONDS);
            LoggerFactory.getLogger(OpcUaServerUtil.class).info("OPC UA服务器启动成功，地址: {}:{}", host, port);
            return server;
        } catch (Exception e) {
            LoggerFactory.getLogger(OpcUaServerUtil.class).error("创建OPC UA服务器失败: {}", e.getMessage(), e);
            throw new RuntimeException("无法启动OPC UA服务器", e);
        }
    }

    /**
     * 节点信息封装类，包含名称、值和备注说明
     */
    public static class NodeInfo {
        public final String nodeName;
        public final Object value;
        public final String description;

        /**
         * 构造节点信息对象
         * @param nodeName 节点名称（必须唯一）
         * @param value 节点值
         * @param description 节点备注说明（可选）
         */
        public NodeInfo(String nodeName, Object value, String description) {
            this.nodeName = nodeName;
            this.value = value;
            this.description = description;
        }
    }

    /**
     * 主方法示例
     */
    public static void main(String[] args) {
        try {
            String ip = "0.0.0.0";  // 绑定所有网络接口
            int port = 12688;       // OPC UA默认端口之一

            // 示例1：创建带备注说明的节点
            List<NodeInfo> nodeInfos = new ArrayList<>();
            nodeInfos.add(new NodeInfo("temperature", 25.5, "设备温度，单位：摄氏度"));
            nodeInfos.add(new NodeInfo("pressure", 101, "设备压力，单位：kPa"));
            nodeInfos.add(new NodeInfo("humidity", 60.0f, "环境湿度，单位：%"));
            nodeInfos.add(new NodeInfo("isRunning", true, "设备运行状态，true表示运行中"));
            nodeInfos.add(new NodeInfo("status", "normal", "设备整体状态描述"));

            // 调用带备注的节点创建方法
            OpcUaServerUtil.updateNodesWithDescription(ip, port, nodeInfos);
            logger.info("示例1：创建带备注的节点完成");

            // 示例2：读取节点备注
            List<String> nodeNames = Arrays.asList("temperature", "pressure", "humidity");
            Map<String, String> descriptions = OpcUaServerUtil.getNodeDescriptions(ip, port, nodeNames);
            logger.info("示例2：节点备注信息: {}", descriptions);

            // 示例3：读取节点值
            Map<String, Object> values = OpcUaServerUtil.readNodes(ip, port, nodeNames);
            logger.info("示例3：节点值信息: {}", values);

            // 保持服务器运行一段时间
            Thread.sleep(600000);
            OpcUaServerUtil.shutdownServer();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LoggerFactory.getLogger(OpcUaServerUtil.class).error("主线程被中断: {}", e.getMessage(), e);
        }
    }

    @Override
    public void onDataItemsCreated(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsCreated(dataItems);
    }

    @Override
    public void onDataItemsModified(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsModified(dataItems);
    }

    @Override
    public void onDataItemsDeleted(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsDeleted(dataItems);
    }

    @Override
    public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) {
        subscriptionModel.onMonitoringModeChanged(monitoredItems);
    }
}
